﻿#include "Termination.h"
#include "Logger.h"

#include <cstdlib>
#include <utility>
#include <exception>
#include <unordered_map>
#include <mutex>

ETERFREE_SPACE_BEGIN

using IDType = Termination::IDType;
using Handler = Termination::Handler;

using Handle = std::terminate_handler;
using Mapper = std::unordered_map<IDType, Handler>;

static std::mutex _exitMutex;
static Mapper _exitMapper;

static std::mutex _quickExitMutex;
static Mapper _quickExitMapper;

static std::mutex _terminateMutex;
static Handle _terminateHandle;
static Mapper _terminateMapper;

void Termination::handleExit()
{
	std::lock_guard lock(_exitMutex);
	for (auto& [_, handler] : _exitMapper)
	{
		try
		{
			handler();
		}
		catch (std::exception& exception)
		{
			Logger::output(Logger::Level::ERROR, \
				std::source_location::current(), \
				exception);
		}
	}
}

void Termination::handleQuickExit()
{
	std::lock_guard lock(_quickExitMutex);
	for (auto& [_, handler] : _quickExitMapper)
	{
		try
		{
			handler();
		}
		catch (std::exception& exception)
		{
			Logger::output(Logger::Level::ERROR, \
				std::source_location::current(), \
				exception);
		}
	}
}

void Termination::handleTerminate()
{
	std::lock_guard lock(_terminateMutex);
	for (auto& [_, handler] : _terminateMapper)
	{
		try
		{
			handler();
		}
		catch (std::exception& exception)
		{
			Logger::output(Logger::Level::ERROR, \
				std::source_location::current(), \
				exception);
		}
	}

	if (_terminateHandle != nullptr)
	{
		try
		{
			_terminateHandle();
		}
		catch (std::exception& exception)
		{
			Logger::output(Logger::Level::ERROR, \
				std::source_location::current(), \
				exception);
		}
	}
}

Termination::Termination() noexcept
{
	try
	{
		std::atexit(handleExit);
	}
	catch (std::exception& exception)
	{
		Logger::output(Logger::Level::ERROR, \
			std::source_location::current(), \
			exception);
	}

	try
	{
		std::at_quick_exit(handleQuickExit);
	}
	catch (std::exception& exception)
	{
		Logger::output(Logger::Level::ERROR, \
			std::source_location::current(), \
			exception);
	}
}

bool Termination::insertExitHandler(IDType _id, \
	const Handler& _handler) noexcept
{
	bool result = false;
	if (not _handler) return result;

	try
	{
		std::unique_lock lock(_exitMutex);
		_exitMapper.insert_or_assign(_id, _handler);
		lock.unlock();

		result = true;
	}
	catch (std::exception& exception)
	{
		Logger::output(Logger::Level::ERROR, \
			std::source_location::current(), \
			exception);
	}
	return result;
}

bool Termination::insertExitHandler(IDType _id, \
	Handler&& _handler) noexcept
{
	bool result = false;
	if (not _handler) return result;

	try
	{
		std::unique_lock lock(_exitMutex);
		_exitMapper.insert_or_assign(_id, \
			std::forward<Handler>(_handler));
		lock.unlock();

		result = true;
	}
	catch (std::exception& exception)
	{
		Logger::output(Logger::Level::ERROR, \
			std::source_location::current(), \
			exception);
	}
	return result;
}

bool Termination::removeExitHandler(IDType _id) noexcept
{
	bool result = false;
	try
	{
		std::lock_guard lock(_exitMutex);
		result = _exitMapper.erase(_id) > 0;
	}
	catch (std::exception& exception)
	{
		Logger::output(Logger::Level::ERROR, \
			std::source_location::current(), exception);
	}
	return result;
}

bool Termination::insertQuickExitHandler(IDType _id, \
	const Handler& _handler) noexcept
{
	bool result = false;
	if (not _handler) return result;

	try
	{
		std::unique_lock lock(_quickExitMutex);
		_quickExitMapper.insert_or_assign(_id, _handler);
		lock.unlock();

		result = true;
	}
	catch (std::exception& exception)
	{
		Logger::output(Logger::Level::ERROR, \
			std::source_location::current(), exception);
	}
	return result;
}

bool Termination::insertQuickExitHandler(IDType _id, \
	Handler&& _handler) noexcept
{
	bool result = false;
	if (not _handler) return result;

	try
	{
		std::unique_lock lock(_quickExitMutex);
		_quickExitMapper.insert_or_assign(_id, \
			std::forward<Handler>(_handler));
		lock.unlock();

		result = true;
	}
	catch (std::exception& exception)
	{
		Logger::output(Logger::Level::ERROR, \
			std::source_location::current(), \
			exception);
	}
	return result;
}

bool Termination::removeQuickExitHandler(IDType _id) noexcept
{
	bool result = false;
	try
	{
		std::lock_guard lock(_quickExitMutex);
		result = _quickExitMapper.erase(_id) > 0;
	}
	catch (std::exception& exception)
	{
		Logger::output(Logger::Level::ERROR, \
			std::source_location::current(), exception);
	}
	return result;
}

bool Termination::insertTerminateHandler(IDType _id, \
	const Handler& _handler) noexcept
{
	bool result = false;
	if (not _handler) return result;

	try
	{
		std::unique_lock lock(_terminateMutex);
		if (_terminateMapper.empty())
			_terminateHandle = std::set_terminate(handleTerminate);

		_terminateMapper.insert_or_assign(_id, _handler);
		lock.unlock();

		result = true;
	}
	catch (std::exception& exception)
	{
		Logger::output(Logger::Level::ERROR, \
			std::source_location::current(), exception);
	}
	return result;
}

bool Termination::insertTerminateHandler(IDType _id, \
	Handler&& _handler) noexcept
{
	bool result = false;
	if (not _handler) return result;

	try
	{
		std::unique_lock lock(_terminateMutex);
		if (_terminateMapper.empty())
			_terminateHandle = std::set_terminate(handleTerminate);

		_terminateMapper.insert_or_assign(_id, \
			std::forward<Handler>(_handler));
		lock.unlock();

		result = true;
	}
	catch (std::exception& exception)
	{
		Logger::output(Logger::Level::ERROR, \
			std::source_location::current(), exception);
	}
	return result;
}

bool Termination::removeTerminateHandler(IDType _id) noexcept
{
	bool result = false;
	try
	{
		std::lock_guard lock(_terminateMutex);
		result = _terminateMapper.erase(_id) > 0;
		if (result and _terminateMapper.empty())
		{
			std::set_terminate(_terminateHandle);
			_terminateHandle = nullptr;
		}
	}
	catch (std::exception& exception)
	{
		Logger::output(Logger::Level::ERROR, \
			std::source_location::current(), exception);
	}
	return result;
}

ETERFREE_SPACE_END
